package com.rs.game.npc;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import com.rs.Settings;
import com.rs.cache.loaders.ItemDefinitions;
import com.rs.cache.loaders.NPCDefinitions;
import com.rs.cores.CoresManager;
import com.rs.game.Animation;
import com.rs.game.Entity;
import com.rs.game.Graphics;
import com.rs.game.Hit;
import com.rs.game.Hit.HitLook;
import com.rs.game.World;
import com.rs.game.WorldTile;
import com.rs.game.item.Item;
import com.rs.game.item.ItemsContainer;
import com.rs.game.npc.Drop.Rarity;
import com.rs.game.npc.combat.NPCCombat;
import com.rs.game.npc.combat.NPCCombatDefinitions;
import com.rs.game.npc.familiar.Familiar;
import com.rs.game.player.Player;
import com.rs.game.player.Skills;
import com.rs.game.player.actions.HerbCleaning;
import com.rs.game.player.actions.HerbCleaning.Herbs;
import com.rs.game.player.content.Burying;
import com.rs.game.player.content.FriendChatsManager;
import com.rs.game.player.content.Burying.Bone;
import com.rs.game.player.content.warriorsguild.Defender;
import com.rs.game.player.controlers.Wilderness;
import com.rs.game.tasks.WorldTask;
import com.rs.game.tasks.WorldTasksManager;
import com.rs.utils.Logger;
import com.rs.utils.MapAreas;
import com.rs.utils.Misc;
import com.rs.utils.NPCBonuses;
import com.rs.utils.NPCCombatDefinitionsL;
import com.rs.utils.NPCDrops;
import com.rs.utils.Utils;

public class NPC extends Entity implements Serializable {

	private static final long serialVersionUID = -4794678936277614443L;

	private int id;
	private WorldTile respawnTile;
	private int mapAreaNameHash;
	private boolean canBeAttackFromOutOfArea;
	private boolean randomwalk;
	private int[] bonuses; // 0 stab, 1 slash, 2 crush,3 mage, 4 range, 5 stab
	// def, blahblah till 9
	private boolean spawned;
	private transient NPCCombat combat;
	public WorldTile forceWalk;

	private long lastAttackedByTarget;
	private boolean cantInteract;
	private int capDamage;
	private int lureDelay;
    private boolean cantFollowUnderCombat;
	private boolean forceAgressive;
	private int forceTargetDistance;
	private boolean forceFollowClose;
	private boolean forceMultiAttacked;
	private boolean noDistanceCheck;

	// npc masks
	private transient Transformation nextTransformation;
	// name changing masks
	private String name;
	private transient boolean changedName;
	private int combatLevel;
	private transient boolean changedCombatLevel;
	private transient boolean locked;
    private transient TimerBar Timerbar;
    
	public NPC(int id, WorldTile tile, int mapAreaNameHash, boolean canBeAttackFromOutOfArea) {
		this(id, tile, mapAreaNameHash, canBeAttackFromOutOfArea, false);
	}

	/*
	 * creates and adds npc
	 */
	public NPC(int id, WorldTile tile, int mapAreaNameHash, boolean canBeAttackFromOutOfArea, boolean spawned) {
		super(tile);
		this.id = id;
		this.respawnTile = new WorldTile(tile);
		this.mapAreaNameHash = mapAreaNameHash;
		this.canBeAttackFromOutOfArea = canBeAttackFromOutOfArea;
		this.setSpawned(spawned);
		combatLevel = -1;
		setHitpoints(getMaxHitpoints());
		setDirection(getRespawnDirection());
		setRandomWalk((getDefinitions().walkMask & 0x2) != 0 || forceRandomWalk(id));
		bonuses = NPCBonuses.getBonuses(id);
		combat = new NPCCombat(this);
		capDamage = -1;
		lureDelay = 12000;
		// npc is inited on creating instance
		initEntity();
		World.addNPC(this);
		World.updateEntityRegion(this);
		// npc is started on creating instance
		loadMapRegions();
		checkMultiArea();
	}
	@Override
	public boolean needMasksUpdate() {
		return super.needMasksUpdate() || nextTransformation != null || changedCombatLevel || changedName;
	}
	public void transformIntoNPC(int id) {
		setNPC(id);
		nextTransformation = new Transformation(id);
	}

	public void setNPC(int id) {
		this.id = id;
		bonuses = NPCBonuses.getBonuses(id);
	}

	@Override
	public void resetMasks() {
		super.resetMasks();
		nextTransformation = null;
		changedCombatLevel = false;
		changedName = false;
	}

	public int getMapAreaNameHash() {
		return mapAreaNameHash;
	}

	public void setCanBeAttackFromOutOfArea(boolean b) {
		canBeAttackFromOutOfArea = b;
	}

	public boolean canBeAttackFromOutOfArea() {
		return canBeAttackFromOutOfArea;
	}

	public NPCDefinitions getDefinitions() {
		return NPCDefinitions.getNPCDefinitions(id);
	}

	public NPCCombatDefinitions getCombatDefinitions() {
		return NPCCombatDefinitionsL.getNPCCombatDefinitions(id);
	}

	@Override
	public int getMaxHitpoints() {
		return getCombatDefinitions().getHitpoints();
	}

	public int getId() {
		return id;
	}

	public void processNPC() {
		if (isDead() || locked) {
			return;
		}
		switch (id) {
		case 1861:
		case 379:
		case 523:
		case 1780:
		case 15568:
		case 519:
		case 9085:
		case 3155:
		case 659:
		case 650:
			setRandomWalk(false);
			break;
		case 549:
		case 551:
		case 550:
		case 546:
		case 5913:
		case 587:
		case 14301:
			setRandomWalk(true);
			break;
		case 13697:
			setName("TrollInvasion");
			break;
		case 14285:
			setName("Server Manager");
			break;
		case 15582: // Hey Presto
			setName("Magic shops");
			break;
		case 15533: // Minni Coop
			setName("Ranging shops");
			break;
		case 14912:
			setName("Melee shops");
			break;
		case 13928:
			setName("Skilling shops");
			break;
		case 4250:
			setName("Plank shop");
			break;
		case 14943:
			setRandomWalk(false);
			setName("Herblore shops");
			break;
		case 548:
			setRandomWalk(false);
			setName("Supply shop");
			break;
		case 520:
			setName("I buy anything");
			break;
		case 11569:
			setName("Quests & Logs");
			break;
		case 4585:
		case 9047:
			setName("Voting Shops");
			break;
		case 14984:
			setName("Training teleports");
			break;
		case 11583:
			setName("Monster teleports");
			break;
		case 14282:
			setName("Server Manager");
			break;
		case 4286:
			setName("Loyalty Manager");
			break;
		}
		if (!combat.process()) { // if not under combat
			if (!isForceWalking()) {// combat still processed for attack delay
				// go down
				// random walk
				if (!cantInteract) {
					if (!checkAgressivity()) {
						if (getFreezeDelay() < Utils.currentTimeMillis()) {
							if (((hasRandomWalk()) && World.getRotation(getPlane(), getX(), getY()) == 0) && Math.random() * 1000.0 < 100.0) {
								int moveX = (int) Math.round(Math.random() * 10.0 - 5.0);
								int moveY = (int) Math.round(Math.random() * 10.0 - 5.0);
								resetWalkSteps();
								if (getMapAreaNameHash() != -1) {
									if (!MapAreas.isAtArea(getMapAreaNameHash(), this)) {
										forceWalkRespawnTile();
										return;
									}
									addWalkSteps(getX() + moveX, getY() + moveY, 5);
								} else
									addWalkSteps(respawnTile.getX() + moveX, respawnTile.getY() + moveY, 5);
							}
						}
					}
				}
			}
		}
		if (isForceWalking()) {
			if (getFreezeDelay() < Utils.currentTimeMillis()) {
				if (getX() != forceWalk.getX() || getY() != forceWalk.getY()) {
					if (!hasWalkSteps())
						addWalkSteps(forceWalk.getX(), forceWalk.getY(), getSize(), true);
					if (!hasWalkSteps()) { // failing finding route
						setNextWorldTile(new WorldTile(forceWalk)); // force
						forceWalk = null; // so ofc reached forcewalk place
					}
				} else
					// walked till forcewalk place
					forceWalk = null;
			}
		}
	}

	@Override
	public void processEntity() {
		super.processEntity();
		processNPC();
	}

	public int getRespawnDirection() {
		NPCDefinitions definitions = getDefinitions();
		if (definitions.anInt853 << 32 != 0 && definitions.respawnDirection > 0 && definitions.respawnDirection <= 8)
			return (4 + definitions.respawnDirection) << 11;
		return 0;
	}

	/*
	 * forces npc to random walk even if cache says no, used because of fake
	 * cache information
	 */
	private static boolean forceRandomWalk(int npcId) {
		switch (npcId) {
		case 11226:
		case 3341:
		case 3342:
		case 3343:
			return true;
		default:
			return false;
		}
	}

	public void sendSoulSplit(final Hit hit, final Entity user) {
		final NPC target = this;
		if (hit.getDamage() > 0)
			World.sendProjectile(user, this, 2263, 11, 11, 20, 5, 0, 0);
		user.heal(hit.getDamage() / 5);
		WorldTasksManager.schedule(new WorldTask() {
			@Override
			public void run() {
				setNextGraphics(new Graphics(2264));
				if (hit.getDamage() > 0)
					World.sendProjectile(target, user, 2263, 11, 11, 20, 5, 0, 0);
			}
		}, 1);
	}

	@Override
	public void handleIngoingHit(final Hit hit) {
		if (capDamage != -1 && hit.getDamage() > capDamage)
			hit.setDamage(capDamage);
		if (hit.getLook() != HitLook.MELEE_DAMAGE && hit.getLook() != HitLook.RANGE_DAMAGE && hit.getLook() != HitLook.MAGIC_DAMAGE)
			return;
		Entity source = hit.getSource();
		if (source == null)
			return;
		if (source instanceof Player) {
			final Player p2 = (Player) source;
			if (p2.getPrayer().hasPrayersOn()) {
				if (p2.getPrayer().usingPrayer(1, 18))
					sendSoulSplit(hit, p2);
				if (hit.getDamage() == 0)
					return;
				if (!p2.getPrayer().isBoostedLeech()) {
					if (hit.getLook() == HitLook.MELEE_DAMAGE) {
						if (p2.getPrayer().usingPrayer(1, 19)) {
							p2.getPrayer().setBoostedLeech(true);
							return;
						} else if (p2.getPrayer().usingPrayer(1, 1)) { // sap
							// att
							if (Utils.getRandom(4) == 0) {
								if (p2.getPrayer().reachedMax(0)) {
									p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your sap curse has no effect.", true);
								} else {
									p2.getPrayer().increaseLeechBonus(0);
									p2.getPackets().sendGameMessage("Your curse drains Attack from the enemy, boosting your Attack.", true);
								}
								p2.setNextAnimation(new Animation(12569));
								p2.setNextGraphics(new Graphics(2214));
								p2.getPrayer().setBoostedLeech(true);
								World.sendProjectile(p2, this, 2215, 35, 35, 20, 5, 0, 0);
								WorldTasksManager.schedule(new WorldTask() {
									@Override
									public void run() {
										setNextGraphics(new Graphics(2216));
									}
								}, 1);
								return;
							}
						} else {
							if (p2.getPrayer().usingPrayer(1, 10)) {
								if (Utils.getRandom(7) == 0) {
									if (p2.getPrayer().reachedMax(3)) {
										p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
									} else {
										p2.getPrayer().increaseLeechBonus(3);
										p2.getPackets().sendGameMessage("Your curse drains Attack from the enemy, boosting your Attack.", true);
									}
									p2.setNextAnimation(new Animation(12575));
									p2.getPrayer().setBoostedLeech(true);
									World.sendProjectile(p2, this, 2231, 35, 35, 20, 5, 0, 0);
									WorldTasksManager.schedule(new WorldTask() {
										@Override
										public void run() {
											setNextGraphics(new Graphics(2232));
										}
									}, 1);
									return;
								}
							}
							if (p2.getPrayer().usingPrayer(1, 14)) {
								if (Utils.getRandom(7) == 0) {
									if (p2.getPrayer().reachedMax(7)) {
										p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
									} else {
										p2.getPrayer().increaseLeechBonus(7);
										p2.getPackets().sendGameMessage("Your curse drains Strength from the enemy, boosting your Strength.", true);
									}
									p2.setNextAnimation(new Animation(12575));
									p2.getPrayer().setBoostedLeech(true);
									World.sendProjectile(p2, this, 2248, 35, 35, 20, 5, 0, 0);
									WorldTasksManager.schedule(new WorldTask() {
										@Override
										public void run() {
											setNextGraphics(new Graphics(2250));
										}
									}, 1);
									return;
								}
							}

						}
					}
					if (hit.getLook() == HitLook.RANGE_DAMAGE) {
						if (p2.getPrayer().usingPrayer(1, 2)) { // sap range
							if (Utils.getRandom(4) == 0) {
								if (p2.getPrayer().reachedMax(1)) {
									p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your sap curse has no effect.", true);
								} else {
									p2.getPrayer().increaseLeechBonus(1);
									p2.getPackets().sendGameMessage("Your curse drains Range from the enemy, boosting your Range.", true);
								}
								p2.setNextAnimation(new Animation(12569));
								p2.setNextGraphics(new Graphics(2217));
								p2.getPrayer().setBoostedLeech(true);
								World.sendProjectile(p2, this, 2218, 35, 35, 20, 5, 0, 0);
								WorldTasksManager.schedule(new WorldTask() {
									@Override
									public void run() {
										setNextGraphics(new Graphics(2219));
									}
								}, 1);
								return;
							}
						} else if (p2.getPrayer().usingPrayer(1, 11)) {
							if (Utils.getRandom(7) == 0) {
								if (p2.getPrayer().reachedMax(4)) {
									p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
								} else {
									p2.getPrayer().increaseLeechBonus(4);
									p2.getPackets().sendGameMessage("Your curse drains Range from the enemy, boosting your Range.", true);
								}
								p2.setNextAnimation(new Animation(12575));
								p2.getPrayer().setBoostedLeech(true);
								World.sendProjectile(p2, this, 2236, 35, 35, 20, 5, 0, 0);
								WorldTasksManager.schedule(new WorldTask() {
									@Override
									public void run() {
										setNextGraphics(new Graphics(2238));
									}
								});
								return;
							}
						}
					}
					if (hit.getLook() == HitLook.MAGIC_DAMAGE) {
						if (p2.getPrayer().usingPrayer(1, 3)) { // sap mage
							if (Utils.getRandom(4) == 0) {
								if (p2.getPrayer().reachedMax(2)) {
									p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your sap curse has no effect.", true);
								} else {
									p2.getPrayer().increaseLeechBonus(2);
									p2.getPackets().sendGameMessage("Your curse drains Magic from the enemy, boosting your Magic.", true);
								}
								p2.setNextAnimation(new Animation(12569));
								p2.setNextGraphics(new Graphics(2220));
								p2.getPrayer().setBoostedLeech(true);
								World.sendProjectile(p2, this, 2221, 35, 35, 20, 5, 0, 0);
								WorldTasksManager.schedule(new WorldTask() {
									@Override
									public void run() {
										setNextGraphics(new Graphics(2222));
									}
								}, 1);
								return;
							}
						} else if (p2.getPrayer().usingPrayer(1, 12)) {
							if (Utils.getRandom(7) == 0) {
								if (p2.getPrayer().reachedMax(5)) {
									p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
								} else {
									p2.getPrayer().increaseLeechBonus(5);
									p2.getPackets().sendGameMessage("Your curse drains Magic from the enemy, boosting your Magic.", true);
								}
								p2.setNextAnimation(new Animation(12575));
								p2.getPrayer().setBoostedLeech(true);
								World.sendProjectile(p2, this, 2240, 35, 35, 20, 5, 0, 0);
								WorldTasksManager.schedule(new WorldTask() {
									@Override
									public void run() {
										setNextGraphics(new Graphics(2242));
									}
								}, 1);
								return;
							}
						}
					}

					// overall

					if (p2.getPrayer().usingPrayer(1, 13)) { // leech defence
						if (Utils.getRandom(10) == 0) {
							if (p2.getPrayer().reachedMax(6)) {
								p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
							} else {
								p2.getPrayer().increaseLeechBonus(6);
								p2.getPackets().sendGameMessage("Your curse drains Defence from the enemy, boosting your Defence.", true);
							}
							p2.setNextAnimation(new Animation(12575));
							p2.getPrayer().setBoostedLeech(true);
							World.sendProjectile(p2, this, 2244, 35, 35, 20, 5, 0, 0);
							WorldTasksManager.schedule(new WorldTask() {
								@Override
								public void run() {
									setNextGraphics(new Graphics(2246));
								}
							}, 1);
							return;
						}
					}
				}
			}
		}

	}

	@Override
	public void reset() {
		super.reset();
		setDirection(getRespawnDirection());
		combat.reset();
		bonuses = NPCBonuses.getBonuses(id); // back to real bonuses
		forceWalk = null;
	}

	@Override
	public void finish() {
		if (hasFinished())
			return;
		setFinished(true);
		World.updateEntityRegion(this);
		World.removeNPC(this);
	}

	public void setRespawnTask() {
		if (!hasFinished()) {
			reset();
			setLocation(respawnTile);
			finish();
		}
		CoresManager.slowExecutor.schedule(new Runnable() {
			@Override
			public void run() {
				try {
					spawn();
				} catch (Throwable e) {
					Logger.handle(e);
				}
			}
		}, getCombatDefinitions().getRespawnDelay() * 600, TimeUnit.MILLISECONDS);
	}

	public void deserialize() {
		if (combat == null)
			combat = new NPCCombat(this);
		spawn();
	}

	public void spawn() {
		setFinished(false);
		World.addNPC(this);
		setLastRegionId(0);
		World.updateEntityRegion(this);
		loadMapRegions();
		checkMultiArea();
	}

	public NPCCombat getCombat() {
		return combat;
	}

	/**
	 * Send the death of an Entity
	 * 
	 */
	@Override
	public void sendDeath(Entity source) {
		final NPCCombatDefinitions defs = getCombatDefinitions();
		resetWalkSteps();
		combat.removeTarget();
		drop();
		setNextAnimation(null);
		WorldTasksManager.schedule(new WorldTask() {
			int loop;

			@Override
			public void run() {
				if (loop == 0) {
					setNextAnimation(new Animation(defs.getDeathEmote()));
				} else if (loop >= defs.getDeathDelay()) {
					reset();
					setLocation(respawnTile);
					finish();
					if (!isSpawned())
						setRespawnTask();
					stop();
				}
				loop++;
			}
		}, 0, 1);
	}

	/**
	 * Drop items when killed
	 * 
	 */
	public void drop() {
		try {
			ArrayList<Drop> drops = NPCDrops.getDrops(id);
			if (drops == null)
				return;
			Player killer = getMostDamageReceivedSourcePlayer();
			if (killer == null)
				return;
			if (this.getId() == 4291 || id == 6081 || id == 4292 || id == 6078) {
				if (Misc.random(1000) > 800)
					World.addGroundItem(Defender.getDefender(killer), getWorldTile(), killer, false, 180, true);
			}
			if (killer.getTask() != null) {
				if (getDefinitions().name.toLowerCase().equalsIgnoreCase(killer.getTask().getName().toLowerCase())) {
					killer.getSkills().addXp(Skills.SLAYER, killer.getTask().getXPAmount());
					killer.getTask().decreaseAmount();
					if (killer.getTask().getTaskAmount() < 1) {
						killer.taskCount++;
						if (killer.taskCount % 50 == 0) {
							killer.setSlayerPoints(killer.getSlayerPoints() + 270);
							killer.getPackets().sendGameMessage("You've completed " + killer.taskCount + " tasks in a row and gain 270 points. Return to a Slayer Master.");
						} else if (killer.taskCount % 10 == 0) {
							killer.setSlayerPoints(killer.getSlayerPoints() + 90);
							killer.getPackets().sendGameMessage("You've completed " + killer.taskCount + " tasks in a row and gain 90 points. Return to a Slayer Master.");
						} else {
							killer.setSlayerPoints(killer.getSlayerPoints() + 18);
							killer.getPackets().sendGameMessage("You've completed " + killer.taskCount + " tasks in a row and gain 18 points. Return to a Slayer Master.");
						}
						killer.setTask(null);
						return;
					}
					killer.getTask().setAmountKilled(killer.getTask().getAmountKilled() + 1);
				}
			}

			if (drops == null || drops.isEmpty())
				return;

			if (Utils.random(100) < 20) {
				Drop charm = new Drop(Rarity.UNCOMMON, com.rs.game.player.content.ItemConstants.charmIds[Utils.random(0, 4)], 1, 1);
				if (killer.getInventory().containsItem(10489, 1) && (killer.getInventory().getFreeSlots() >= 1)) {
					killer.getPackets().sendGameMessage("Your charm magnet picks up a " + ItemDefinitions.getItemDefinitions(charm.getItemId()).getName().toLowerCase() + ".", true);
					killer.getInventory().addItem(charm.getItemId(), 1);
				} else {
					sendDrop(killer, charm);
				}
			}

			Drop finalDrop = null;
			Drop rareDrop = null;

			int roll = (Utils.random(5000));
			ArrayList<Drop> possibleDrops = new ArrayList<Drop>();
			ArrayList<Drop> rareDropTable = new ArrayList<Drop>();

			if (hasAccessToRareTable(killer)) {
				int rareroll = (Utils.random(5000));
				ArrayList<Drop> rareTable = NPCDrops.getDrops(50000);
				for (Drop rare : rareTable) {
					if (rareroll < 25 && rare.getRarity() == Rarity.ULTRA_RARE) {
						rareDropTable.add(rare);
					} else if (rareroll < 75 && rare.getRarity() == Rarity.VERY_RARE) {
						rareDropTable.add(rare);
					} else if (rareroll < 250 && rare.getRarity() == Rarity.RARE) {
						rareDropTable.add(rare);
					} else if (rareroll < 1000 && rare.getRarity() == Rarity.UNCOMMON) {
						rareDropTable.add(rare);
					} else if (rareroll < 4500 && rare.getRarity() == Rarity.COMMON) {
						rareDropTable.add(rare);
					}
				}
			}

			for (Drop drop : drops) {
				if (drop.getRarity() == Rarity.ALWAYS) {
					sendDrop(killer, drop);
				} else {
					if (roll < 25 && drop.getRarity() == Rarity.ULTRA_RARE) {
						possibleDrops.add(drop);
					} else if (roll < 75 && drop.getRarity() == Rarity.VERY_RARE) {
						possibleDrops.add(drop);
					} else if (roll < 250 && drop.getRarity() == Rarity.RARE) {
						possibleDrops.add(drop);
					} else if (roll < 1000 && drop.getRarity() == Rarity.UNCOMMON) {
						possibleDrops.add(drop);
					} else if (roll < 4500 && drop.getRarity() == Rarity.COMMON) {
						possibleDrops.add(drop);
					}
				}
			}
			if (rareDropTable.size() > 0)
				rareDrop = rareDropTable.get(Utils.random(rareDropTable.size()));
			if (rareDrop != null) {
				if (killer.getEquipment().getRingId() != -1 && ItemDefinitions.getItemDefinitions(killer.getEquipment().getRingId()).getName().toLowerCase().contains("ring of wealth")) {
					killer.getPackets().sendGameMessage("<col=FACC2E>Your ring of wealth shines brightly!");
				}
				sendDrop(killer, rareDrop);
			}
			if (possibleDrops.size() > 0)
				finalDrop = possibleDrops.get(Utils.random(possibleDrops.size()));
			if (finalDrop != null)
				sendDrop(killer, finalDrop);
		} catch (Exception e) {
			e.printStackTrace();
		} catch (Error e) {
			e.printStackTrace();
		}
	}

	public static boolean accessToRareTable(Player player, int npcId) {
		if (npcId == 13447 || npcId == 13448 || npcId == 13449 || npcId == 13450 || npcId == 6203 || npcId == 6222 || 
			npcId == 6247 || npcId == 6260)
			return true;
		int chance = Utils.getRandom(1000);
		chance -= NPCDefinitions.getNPCDefinitions(npcId).combatLevel/4;
		if (player.getEquipment().getRingId() != -1 && ItemDefinitions.getItemDefinitions(player.getEquipment().getRingId()).getName().toLowerCase().contains("ring of wealth")) {
			chance -= 10;
		}
		if (chance <= 10) {
			return true;
		}
		return false;
	}

	public boolean hasAccessToRareTable(Player killer) {
		int chance = Utils.getRandom(1000);
		chance -= getDefinitions().combatLevel/4;
		if (killer.getEquipment().getRingId() != -1 && ItemDefinitions.getItemDefinitions(killer.getEquipment().getRingId()).getName().toLowerCase().contains("ring of wealth")) {
			chance -= 10;
		}
		if (chance <= 10) {
			if (killer.getEquipment().getRingId() != -1 && ItemDefinitions.getItemDefinitions(killer.getEquipment().getRingId()).getName().toLowerCase().contains("ring of wealth")) {
				killer.getPackets().sendGameMessage("<col=FACC2E>Your ring of wealth shines brightly!");
			}
			return true;
		}
		return false;
	}
	
	public void sendDrop(Player player, Drop drop) {
		if (player.getTemporaryAttributes().get("sendingDropsToBank") != null && player.getTemporaryAttributes().get("sendingDropsToBank") == Boolean.TRUE) {
			sendDropDirectlyToBank(player, drop);
			return;
		}
		int size = getSize();
		Item item;

		if(drop.getMaxAmount() > 1)
			item = new Item(drop.getItemId(), drop.getMinAmount()+Utils.getRandom(drop.getMaxAmount()));
		else
			item = new Item(drop.getItemId(), 1);

		if (player.getRights() == 2 && (drop.getRarity() == Rarity.RARE || drop.getRarity() == Rarity.VERY_RARE)) 
			player.getPackets().sendGameMessage("DROPPED ITEM: Name: "+item.getDefinitions().getName()+" Rarity: "+drop.getRarity()+", Item: ("+drop.getItemId()+", "+item.getAmount()+")");

		if (player.getInventory().containsItem(18337, 1)) {
			Bone bone = Bone.forId(item.getId());
			if (bone != null) {
				player.getSkills().addXp(Skills.PRAYER, bone.getExperience());
				Burying.handleNecklaces(player, bone.getId());
				return;
			}
		}
		FriendChatsManager fc = player.getCurrentFriendChat();
		if (player.lootshareEnabled()) {
			if (fc != null) {
				CopyOnWriteArrayList<Player> players = fc.getPlayers();
				CopyOnWriteArrayList<Player> playersWithLs = new CopyOnWriteArrayList<Player>();
				for (Player p : players) {
					if (p.lootshareEnabled()
							&& p.getRegionId() == player.getRegionId()) 
						playersWithLs.add(p);
				}
				Player luckyPlayer = playersWithLs
						.get((int) (Math.random() * playersWithLs.size())); 
				World.addGroundItem(item, new WorldTile(getCoordFaceX(size),
						getCoordFaceY(size), getPlane()), luckyPlayer, false,
						180, true);
				luckyPlayer.sendMessage(String.format(
						"<col=115b0d>You received: %sx %s.</col>",
						item.getAmount(), getName()));
				for (Player p : playersWithLs) {
					if (!p.equals(luckyPlayer))
						p.sendMessage(String.format("%s received: %sx %s.",
								luckyPlayer.getDisplayName(), item.getAmount(),
								getName()));
				}
				return;
			}
		}
		for (int itemId : Settings.RARE_DROPS) {
			if (drop.getItemId() == itemId) {
				World.sendWorldMessage("<img=12><col=fd8f3e>News: " + player.getDisplayName() + " has received " + getName() + " drop!", false);
			}
			
		}		
		if (player.getInventory().containsItem(19675, 1)) {
			Herbs herb = HerbCleaning.getHerb(item.getId());
			if (herb != null && (player.getSkills().getLevel(Skills.HERBLORE) >= herb.getLevel())) {
				player.getSkills().addXp(Skills.HERBLORE, herb.getExperience() * 2);
				return;
			}
		}

		World.addGroundItem(item, new WorldTile(getCoordFaceX(size), getCoordFaceY(size), getPlane()), player, false, 60, true);
	}
	public static Item dropToItem(Drop drop) {
		Item item = null;
		if(drop.getMaxAmount() > 1)
			item = new Item(drop.getItemId(), drop.getMinAmount()+Utils.getRandom(drop.getMaxAmount()));
		else
			item = new Item(drop.getItemId(), 1);
		return item;
	}
	
	public static void sendDropDirectlyToBank(Player player, Drop drop) {
		Item item = null;
		if(drop.getMaxAmount() > 1)
			item = new Item(drop.getItemId(), drop.getMinAmount()+Utils.getRandom(drop.getMaxAmount()));
		else
			item = new Item(drop.getItemId(), 1);
		player.getBank().addItem(item.getId(), item.getAmount(), true);
	}
	
	public static void displayDropsFor(Player player, int npcId, int npcAmount) {
		ItemsContainer<Item> dropCollection = new ItemsContainer<Item>(506, true);
		
		for (int i = 0;i < npcAmount;i++) {
			ArrayList<Drop> drops = NPCDrops.getDrops(npcId);
			if (drops == null || drops.isEmpty())
				return;
			
			if (Utils.random(100) < 20) {
				Drop charm = new Drop(Rarity.UNCOMMON, com.rs.game.player.content.ItemConstants.charmIds[Utils.random(0, 4)], 1, 1);
				dropCollection.add(dropToItem(charm));
			}
			
			Drop finalDrop = null;
			Drop rareDrop = null;
	
			int roll = (Utils.random(5000));
			int rareroll = (Utils.random(5000));
			ArrayList<Drop> possibleDrops = new ArrayList<Drop>();
			ArrayList<Drop> rareDropTable = new ArrayList<Drop>();
			
			if (accessToRareTable(player, npcId)) {
				ArrayList<Drop> rareTable = NPCDrops.getDrops(50000);
				for (Drop rare : rareTable) {
					if (rareroll < 25 && rare.getRarity() == Rarity.ULTRA_RARE) {
						rareDropTable.add(rare);
					} else if (rareroll < 75 && rare.getRarity() == Rarity.VERY_RARE) {
						rareDropTable.add(rare);
					} else if (rareroll < 250 && rare.getRarity() == Rarity.RARE) {
						rareDropTable.add(rare);
					} else if (rareroll < 1000 && rare.getRarity() == Rarity.UNCOMMON) {
						rareDropTable.add(rare);
					} else if (rareroll < 4500 && rare.getRarity() == Rarity.COMMON) {
						rareDropTable.add(rare);
					}
				}
			}
	
			for (Drop drop : drops) {
				if (drop.getRarity() == Rarity.ALWAYS) {
					dropCollection.add(dropToItem(drop));
				} else {
					if (roll < 25 && drop.getRarity() == Rarity.ULTRA_RARE) {
						possibleDrops.add(drop);
					} else if (roll < 75 && drop.getRarity() == Rarity.VERY_RARE) {
						possibleDrops.add(drop);
					} else if (roll < 250 && drop.getRarity() == Rarity.RARE) {
						possibleDrops.add(drop);
					} else if (roll < 1000 && drop.getRarity() == Rarity.UNCOMMON) {
						possibleDrops.add(drop);
					} else if (roll < 4500 && drop.getRarity() == Rarity.COMMON) {
						possibleDrops.add(drop);
					}
				}
			}
			if (possibleDrops.size() > 0)
				finalDrop = possibleDrops.get(Utils.random(possibleDrops.size()));
			if (finalDrop != null)
				dropCollection.add(dropToItem(finalDrop));
			if (rareDropTable.size() > 0)
				rareDrop = rareDropTable.get(Utils.random(rareDropTable.size()));
			if (rareDrop != null)
				dropCollection.add(dropToItem(rareDrop));
		}
		
		player.getTemporaryAttributes().put("viewingOtherBank", Boolean.TRUE);
		player.getInterfaceManager().sendInterface(762);
		player.getPackets().sendItems(95, dropCollection);
		player.getPackets().sendIComponentSettings(762, 95, 0, 516, 2622718);
		player.getPackets().sendIComponentSettings(763, 0, 0, 27, 2425982);
		player.getPackets().sendConfigByFile(4893, 1);
	}
	@Override
	public int getSize() {
		return getDefinitions().size;
	}

	public int getMaxHit() {
		return getCombatDefinitions().getMaxHit();
	}

	public int[] getBonuses() {
		return bonuses;
	}

	@Override
	public double getMagePrayerMultiplier() {
		return 0;
	}

	@Override
	public double getRangePrayerMultiplier() {
		return 0;
	}

	@Override
	public double getMeleePrayerMultiplier() {
		return 0;
	}

	public WorldTile getRespawnTile() {
		return respawnTile;
	}

	public boolean isUnderCombat() {
		return combat.underCombat();
	}

	@Override
	public void setAttackedBy(Entity target) {
		super.setAttackedBy(target);
		if (target == combat.getTarget() && !(combat.getTarget() instanceof Familiar))
			lastAttackedByTarget = Utils.currentTimeMillis();
	}

	public boolean canBeAttackedByAutoRelatie() {
		return Utils.currentTimeMillis() - lastAttackedByTarget > lureDelay;
	}

	public boolean isForceWalking() {
		return forceWalk != null;
	}

	public void setTarget(Entity entity) {
		if (isForceWalking()) // if force walk not gonna get target
			return;
		combat.setTarget(entity);
		lastAttackedByTarget = Utils.currentTimeMillis();
	}

	public void removeTarget() {
		if (combat.getTarget() == null)
			return;
		combat.removeTarget();
	}

	public void forceWalkRespawnTile() {
		setForceWalk(respawnTile);
	}

	public void setForceWalk(WorldTile tile) {
		resetWalkSteps();
		forceWalk = tile;
	}

	public boolean hasForceWalk() {
		return forceWalk != null;
	}

	public ArrayList<Entity> getPossibleTargets() {
		ArrayList<Entity> possibleTarget = new ArrayList<Entity>();
		for (int regionId : getMapRegionsIds()) {
			List<Integer> playerIndexes = World.getRegion(regionId).getPlayerIndexes();
			if (playerIndexes != null) {
				for (int playerIndex : playerIndexes) {
					Player player = World.getPlayers().get(playerIndex);
					if (player == null || player.isDead() || player.hasFinished() || !player.isRunning() || !player.withinDistance(this, forceTargetDistance > 0 ? forceTargetDistance : (getCombatDefinitions().getAttackStyle() == NPCCombatDefinitions.MELEE ? 4 : getCombatDefinitions().getAttackStyle() == NPCCombatDefinitions.SPECIAL ? 64 : 8)) || (!forceMultiAttacked && (!isAtMultiArea() || !player.isAtMultiArea()) && player.getAttackedBy() != this && (player.getAttackedByDelay() > Utils.currentTimeMillis() || player.getFindTargetDelay() > Utils.currentTimeMillis())) || !clipedProjectile(player, false) || (!forceAgressive && !Wilderness.isAtWild(this) && player.getSkills().getCombatLevelWithSummoning() >= getCombatLevel() * 2))
						continue;
					possibleTarget.add(player);
				}
			}
		}
		return possibleTarget;
	}

	public boolean checkAgressivity() {
		// if(!(Wilderness.isAtWild(this) &&
		// getDefinitions().hasAttackOption())) {
		if (!forceAgressive) {
			NPCCombatDefinitions defs = getCombatDefinitions();
			if (defs.getAgressivenessType() == NPCCombatDefinitions.PASSIVE)
				return false;
		}
		// }
		ArrayList<Entity> possibleTarget = getPossibleTargets();
		if (!possibleTarget.isEmpty()) {
			Entity target = possibleTarget.get(Utils.random(possibleTarget.size()));
			setTarget(target);
			target.setAttackedBy(target);
			target.setFindTargetDelay(Utils.currentTimeMillis() + 10000);
			return true;
		}
		return false;
	}

	public boolean isCantInteract() {
		return cantInteract;
	}

	public void setCantInteract(boolean cantInteract) {
		this.cantInteract = cantInteract;
		if (cantInteract)
			combat.reset();
	}

	public int getCapDamage() {
		return capDamage;
	}

	public void setCapDamage(int capDamage) {
		this.capDamage = capDamage;
	}

	public int getLureDelay() {
		return lureDelay;
	}

	public void setLureDelay(int lureDelay) {
		this.lureDelay = lureDelay;
	}

	public boolean isCantFollowUnderCombat() {
		return cantFollowUnderCombat;
	}

	public void setCantFollowUnderCombat(boolean canFollowUnderCombat) {
		this.cantFollowUnderCombat = canFollowUnderCombat;
	}

	public Transformation getNextTransformation() {
		return nextTransformation;
	}

	@Override
	public String toString() {
		return getDefinitions().name + " - " + id + " - " + getX() + " " + getY() + " " + getPlane();
	}

	public boolean isForceAgressive() {
		return forceAgressive;
	}

	public void setForceAgressive(boolean forceAgressive) {
		this.forceAgressive = forceAgressive;
	}

	public int getForceTargetDistance() {
		return forceTargetDistance;
	}

	public void setForceTargetDistance(int forceTargetDistance) {
		this.forceTargetDistance = forceTargetDistance;
	}

	public boolean isForceFollowClose() {
		return forceFollowClose;
	}

	public void setForceFollowClose(boolean forceFollowClose) {
		this.forceFollowClose = forceFollowClose;
	}

	public boolean isForceMultiAttacked() {
		return forceMultiAttacked;
	}

	public void setForceMultiAttacked(boolean forceMultiAttacked) {
		this.forceMultiAttacked = forceMultiAttacked;
	}

	public boolean hasRandomWalk() {
		return randomwalk;
	}

	public void setRandomWalk(boolean forceRandomWalk) {
		this.randomwalk = forceRandomWalk;
	}

	public String getCustomName() {
		return name;
	}

	public void setName(String string) {
		this.name = getDefinitions().name.equals(string) ? null : string;
		changedName = true;
	}

	public int getCustomCombatLevel() {
		return combatLevel;
	}

	public int getCombatLevel() {
		return combatLevel >= 0 ? combatLevel : getDefinitions().combatLevel;
	}

	public String getName() {
		return name != null ? name : getDefinitions().name;
	}

	public void setCombatLevel(int level) {
		combatLevel = getDefinitions().combatLevel == level ? -1 : level;
		changedCombatLevel = true;
	}

	public boolean hasChangedName() {
		return changedName;
	}

	public boolean hasChangedCombatLevel() {
		return changedCombatLevel;
	}

	public WorldTile getMiddleWorldTile() {
		int size = getSize();
		return new WorldTile(getCoordFaceX(size), getCoordFaceY(size), getPlane());
	}

	public boolean isSpawned() {
		return spawned;
	}

	public void setSpawned(boolean spawned) {
		this.spawned = spawned;
	}

	public boolean isNoDistanceCheck() {
		return noDistanceCheck;
	}

	public void setNoDistanceCheck(boolean noDistanceCheck) {
		this.noDistanceCheck = noDistanceCheck;
	}

	public boolean withinDistance(Player tile, int distance) {
		return super.withinDistance(tile, distance);
	}

	/**
	 * Gets the locked.
	 * 
	 * @return The locked.
	 */
	public boolean isLocked() {
		return locked;
	}

	/**
	 * Sets the locked.
	 * 
	 * @param locked
	 *            The locked to set.
	 */
	public void setLocked(boolean locked) {
		this.locked = locked;
	}

	public TimerBar getNextTimerbar() {
		return Timerbar;
	}

	public void setNextTimerbar(TimerBar timerbar) {
		Timerbar = timerbar;
	}
}
